package algeff

import (
	"fmt"
	"goalgeff/coro"
)

type Ctx struct{ inner coro.Ctx }

type Procedure[R any] func(Ctx) R

func (f Procedure[R]) Run() R {
	return Run(f)
}

func Run[R any](f Procedure[R]) R {
	coro := coro.Create(func(ctx coro.Ctx) R {
		return f(Ctx{ctx})
	})
	r, end := coro.Resume(nil)
	if !end {
		panic(fmt.Sprintf("unexpected effect %#v", r))
	}
	return r.(R)
}

func Perform[R any, E Effect[R]](ctx Ctx, e E) R {
	return ctx.inner.Yield(e).(R)
}

func Handle[R1, R2 any](f Procedure[R1], h Handlers[R1, R2]) Procedure[R2] {
	coro := coro.Create(func(ctx coro.Ctx) R1 {
		return f(Ctx{ctx})
	})
	return func(ctx Ctx) R2 { return handle(coro, h, ctx, nil).(R2) }
}

func handle[R1, R2 any](coro coro.Coro[R1], h Handlers[R1, R2], ctx Ctx, o any) any {
	called := false
	check := func() {
		if called {
			panic("continuation has been already called")
		}
	}
	cont := func(o any) any {
		check()
		called = true
		return handle(coro, h, ctx, o)
	}

loop:
	for {
		e, end := coro.Resume(o)
		if end {
			return h.returns(ctx, e.(R1))
		}
		for _, h := range h.handlers {
			var c bool
			o, c = h.inner(e, ctx, cont)
			if o != nil {
				if !c {
					return o
				}
				check()
				continue loop
			}
		}
		o = ctx.inner.Yield(e)
	}
}
